from gettext import gettext as _
import deskbar.interfaces.Module
import deskbar.interfaces.Match
import deskbar.interfaces.Action
from deskbar.core.Utils import load_icon, url_show
from deskbar.core.GconfStore import GconfStore
import urllib
import hashlib # python >= 2.5 required
import xml.dom.minidom
import gtk

HANDLERS = ["RTMHandler"]

RTM_API_KEY = "7e19701e94d2fca8c6dcc3f7127ed1ba"
RTM_SHARED_SECRET = "847a9dca88c6004f"
RTM_SERVICE_METHODS = "http://www.rememberthemilk.com/services/rest/"
RTM_SERVICE_AUTH = "http://www.rememberthemilk.com/services/auth/"
RTM_HOME = "http://www.rememberthemilk.com/home/"

GCONF_AUTH_TOKEN_KEY = GconfStore.GCONF_DIR + "/rtm/auth_token"
GCONF_USERNAME_KEY = GconfStore.GCONF_DIR + "/rtm/username" 

class RTMException (Exception):
	pass

class RTM:
    
    def __init__(self):
        self._gconf = GconfStore.get_instance().get_client()
        self._gconf.notify_add(GCONF_AUTH_TOKEN_KEY, self.__on_auth_token_changed)
    
        self._auth_token = self._gconf.get_string(GCONF_AUTH_TOKEN_KEY)
        
    def __on_auth_token_changed(self, x, y, z, a):
        if z != None:
            self.set_auth_token(z.value.get_string())
        
    def set_frob(self, frob):
        self._frob = frob
        
    def set_auth_token(self, token):
        self._auth_token = token
        
    def get_auth_token(self):
        return self._auth_token
     
    def get_auth_url(self, perms):
        """
        Give this URL to the user that he can give permission to this application
        
        @param token: Can be read, write or delete
        """
        frob = self.auth_get_frob()
        args = {'perms': perms}
        return (self._get_url(RTM_SERVICE_AUTH, args, False, frob), frob)
    
    def get_signature(self, args):
        """
        Returns value of api_sig parameter
        
        @type args: dict
        """
        sig = RTM_SHARED_SECRET
        # Keys must be appended in alphabetical order
        keys = args.keys()
        keys.sort()
        for key in keys:
            sig += key + str(args[key])
        return hashlib.md5(sig).hexdigest()
    
    def _sign_args(self, args, with_auth_token=True, with_frob=None):
        """
        @type args: dict
        @param with_auth_token: Whether the auth token should be appended
        @param with_frob: Optionally append given frob
        """
        if with_frob != None:
            args['frob'] = with_frob
        if with_auth_token:
            args['auth_token'] = self._auth_token
        args['api_key'] = RTM_API_KEY
        args['api_sig'] = self.get_signature(args)
        return args
    
    def _get_url(self, base_url, args, with_auth_token=True, with_frob=None):
        args = self._sign_args(args, with_auth_token, with_frob)
        return base_url + '?' + urllib.urlencode(args)
    
    def _raise_error(self, dom):
        err = dom.getElementsByTagName('err')[0]
        code = err.getAttribute('code')
        msg = err.getAttribute('msg')
        raise RTMException, "%s: %s" % (code, msg)
        
    def _wrap_tasks(self, rsp):
        """
        Wrap XML tree in python classes
        """
        tasks = []
        for list_node in rsp.getElementsByTagName("list"):
            list = self._wrap_list(list_node)
            tasks.append(list)
            
        return tasks
        
    def _wrap_list(self, list_node):
        list = List( list_node.getAttribute("id") )
        
        for taskseries_node in list_node.getElementsByTagName("taskseries"):
            taskseries = self._wrap_taskseries(taskseries_node)
            list.taskseries.append(taskseries)
                
        return list
         
    def _wrap_taskseries(self, taskseries_node):
        taskseries = TaskSeries()
        
        taskseries.id = taskseries_node.getAttribute("id")
        taskseries.created = taskseries_node.getAttribute("created")
        taskseries.modified = taskseries_node.getAttribute("modified")
        taskseries.name = taskseries_node.getAttribute("name")
        taskseries.source = taskseries_node.getAttribute("source")
        taskseries.url = taskseries_node.getAttribute("url")
        taskseries.location_id = taskseries_node.getAttribute("location_id")
        
        for task_node in taskseries_node.getElementsByTagName("task"):
            task = self._wrap_task(task_node)
            taskseries.task.append(task)
            
        return taskseries
    
    def _wrap_task(self, task_node):
        task = Task()
        
        task.id = task_node.getAttribute("id")
        task.due = task_node.getAttribute("due")
        task.has_due_time = bool(task_node.getAttribute("has_due_time"))
        task.added = task_node.getAttribute("added")
        task.completed = task_node.getAttribute("completed")
        task.deleted = task_node.getAttribute("deleted")
        task.priority = task_node.getAttribute("priority")
        task.postponed = task_node.getAttribute("postponed")
        task.estimate = task_node.getAttribute("estimate")
        
        return task
        
    def get_response(self, url):
        dom = xml.dom.minidom.parse( urllib.urlopen( url ) )
        rsp = dom.getElementsByTagName("rsp")[0]
        stat = rsp.getAttribute('stat')
        if stat == 'ok':
            return rsp
        else:
            self._raise_error(dom)
   
    def auth_get_frob(self):
        args = {'method': 'rtm.auth.getFrob'}
        url = self._get_url(RTM_SERVICE_METHODS, args, False)
        
        rsp = self.get_response(url)
        return rsp.getElementsByTagName("frob")[0].childNodes[0].data
            
    def auth_get_token(self, frob):
        args = {'method': 'rtm.auth.getToken'}
        url = self._get_url(RTM_SERVICE_METHODS, args, False, frob)
        
        rsp = self.get_response(url)
        return rsp.getElementsByTagName("token")[0].childNodes[0].data
    
    def auth_check_token(self, token):
        args = {'method': 'rtm.auth.checkToken', 'auth_token': token}
        url = self._get_url(RTM_SERVICE_METHODS, args, False)
        
        try:
            rsp = self.get_response(url)
            info = {}
            info['token'] = rsp.getElementsByTagName("token")[0].childNodes[0].data
            info['perms'] = rsp.getElementsByTagName("perms")[0].childNodes[0].data
            user_node = rsp.getElementsByTagName("user")[0]
            info['user'] = {'id': user_node.getAttribute('id'),
                'username': user_node.getAttribute('username'),
                'fullname': user_node.getAttribute('fullname')}
            return info
        except Exception, msg:
            return False
    
    def lists_get_list(self):
        args = {'method': 'rtm.lists.getList'}    
        url = self._get_url(RTM_SERVICE_METHODS, args)
        
        rsp = self.get_response(url)
        
        lists = []
        for list_node in rsp.getElementsByTagName("list"):
            list_id = list_node.getAttribute("id")
            list_name = list_node.getAttribute("name")
            lists.append( List(list_id, list_name) )
        return lists
    
    def tasks_add(self, timeline, name, list_id=None, parse=False):
        args = {'method': 'rtm.tasks.add',
                'timeline': timeline,
                'name': name}
        if list_id != None:
            args['list_id'] = list_id
        if parse:
            args['parse'] = 1
        url = self._get_url(RTM_SERVICE_METHODS, args)
                       
        rsp = self.get_response(url)
        return self._wrap_list(rsp)
   
    def tasks_complete(self, timeline, list_id, taskseries_id, task_id):
        args = {'method': 'rtm.tasks.complete',
                'timeline': timeline,
                'list_id': list_id,
                'taskseries_id': taskseries_id,
                'task_id': task_id}
        url = self._get_url(RTM_SERVICE_METHODS, args)
        rsp = self.get_response(url)
        return self._wrap_list(rsp)
        
    def tasks_delete(self, timeline, list_id, taskseries_id, task_id):
        args = {'method': 'rtm.tasks.delete',
                'timeline': timeline,
                'list_id': list_id,
                'taskseries_id': taskseries_id,
                'task_id': task_id}
        url = self._get_url(RTM_SERVICE_METHODS, args)
        rsp = self.get_response(url)
        return self._wrap_list(rsp)
     
    def tasks_get_list(self, list_id=None, filter=None, last_sync=None):
        """
        @see: http://www.rememberthemilk.com/services/api/methods/rtm.tasks.getList.rtm
        """
        args = {'method': 'rtm.tasks.getList'}
        if list_id != None:
            args['list_id'] = list_id
        if filter != None:
            args['filter'] = filter
        if last_sync != None:
            args['last_sync'] = last_sync
        url = self._get_url(RTM_SERVICE_METHODS, args)
                       
        rsp = self.get_response(url)
        return self._wrap_tasks(rsp)
   
    def tasks_move_priority(self, timeline, list_id, taskseries_id, task_id, direction):
        args = {'method': 'rtm.tasks.movePriority',
                'timeline': timeline,
                'list_id': list_id,
                'taskseries_id': taskseries_id,
                'task_id': task_id,
                'direction': direction}
        url = self._get_url(RTM_SERVICE_METHODS, args)
        rsp = self.get_response(url)
        return self._wrap_list(rsp)
        
        
    def tasks_move_to(self, timeline, from_list_id, to_list_id, taskseries_id, task_id):
        args = {'method': 'rtm.tasks.moveTo',
                'timeline': timeline,
                'from_list_id': from_list_id,
                'to_list_id': to_list_id,
                'taskseries_id': taskseries_id,
                'task_id': task_id}
        url = self._get_url(RTM_SERVICE_METHODS, args)
        rsp = self.get_response(url)
        return self._wrap_list(rsp)
     
    def tasks_postpone(self, timeline, list_id, taskseries_id, task_id):
        args = {'method': 'rtm.tasks.postpone',
                'timeline': timeline,
                'list_id': list_id,
                'taskseries_id': taskseries_id,
                'task_id': task_id}
        url = self._get_url(RTM_SERVICE_METHODS, args)
        rsp = self.get_response(url)
        return self._wrap_list(rsp)
    
    def timelines_create(self):
        args= {'method': 'rtm.timelines.create'}
        url = self._get_url(RTM_SERVICE_METHODS, args)
        rsp = self.get_response(url)
        return rsp.getElementsByTagName("timeline")[0].childNodes[0].data
    
class List:
    
    def __init__(self, id="", name=""):
        self.id = id
        self.name = name
        self.taskseries = []
    
class TaskSeries:
    
    def __init__(self):
        # Arguments
        self.id = ""
        self.created = ""
        self.modified = ""
        self.name = ""
        self.source = ""
        self.url = ""
        self.location_id = ""
        
        # Child elements
        self.tags = None
        self.participants = None
        self.notes = None
        self.task = []
        
class Task:
    
    def __init__(self):
        self.id = ""
        self.due = ""
        self.has_due_time = ""
        self.added = ""
        self.completed = ""
        self.deleted = ""
        self.priority = ""
        self.postponed = ""
        self.estimate = ""

# ============ END RTM ============ 
RTM_CLIENT = RTM()

class RTMAction(deskbar.interfaces.Action):
    def __init__(self, name, list_id, taskseries_id, task_id):
        deskbar.interfaces.Action.__init__(self, name)
        self._list_id = list_id
        self._taskseries_id = taskseries_id
        self._task_id = task_id
    
class RTMViewTaskAction(RTMAction):
    def __init__(self, name, list_id, taskseries_id, task_id):
        RTMAction.__init__(self, name, list_id, taskseries_id, task_id)
        
    def get_verb(self):
        return _("View task <i>%(name)s</i>")
    
    def get_icon(self):
        return "gtk-find"
    
    def activate(self, text=None):
        gconf = GconfStore.get_instance().get_client()
        url = RTM_HOME + "%s/%s/%s" % (gconf.get_string(GCONF_USERNAME_KEY), self._list_id, self._task_id)
        url_show(url)
       
class RTMMarkCompleteAction(RTMAction):
    def __init__(self, name, list_id, taskseries_id, task_id):
        RTMAction.__init__(self, name, list_id, taskseries_id, task_id)
        
    def get_verb(self):
        return _("Mark task <i>%(name)s</i> as complete")
    
    def get_hash(self):
        return "complete"
    
    def get_icon(self):
        return gtk.STOCK_APPLY
    
    def activate(self, text=None):
        timeline = RTM_CLIENT.timelines_create()
        RTM_CLIENT.tasks_complete(timeline, self._list_id, self._taskseries_id, self._task_id)

class RTMPostponeAction(RTMAction):
    def __init__(self, name, list_id, taskseries_id, task_id):
        RTMAction.__init__(self, name, list_id, taskseries_id, task_id)
        
    def get_verb(self):
        return _("Postpone task <i>%(name)s</i>")
    
    def get_hash(self):
        return "postpone"
    
    def get_icon(self):
        return "go-next"

    def activate(self, text=None):
        timeline = RTM_CLIENT.timelines_create()
        RTM_CLIENT.tasks_postpone(timeline, self._list_id, self._taskseries_id, self._task_id)

class RTMDeleteAction(RTMAction):
    def __init__(self, name, list_id, taskseries_id, task_id):
        RTMAction.__init__(self, name, list_id, taskseries_id, task_id)
        
    def get_verb(self):
        return _("Delete task <i>%(name)s</i>")
    
    def get_hash(self):
        return "delete"
    
    def get_icon(self):
        return gtk.STOCK_DELETE
    
    def activate(self, text=None):
        timeline = RTM_CLIENT.timelines_create()
        RTM_CLIENT.tasks_delete(timeline, self._list_id, self._taskseries_id, self._task_id)

class RTMMovePriorityUpAction(RTMAction):
    def __init__(self, name, list_id, taskseries_id, task_id):
        RTMAction.__init__(self, name, list_id, taskseries_id, task_id)
        
    def get_verb(self):
        return _("Move priority of task <i>%(name)s</i> up")
    
    def get_hash(self):
        return "move-priority-up"
    
    def get_icon(self):
        return gtk.STOCK_GO_UP
    
    def activate(self, text=None):
        timeline = RTM_CLIENT.timelines_create()
        RTM_CLIENT.tasks_move_priority(timeline, self._list_id, self._taskseries_id, self._task_id, "up")

class RTMMovePriorityDownAction(RTMAction):
    def __init__(self, name, list_id, taskseries_id, task_id):
        RTMAction.__init__(self, name, list_id, taskseries_id, task_id)
        
    def get_verb(self):
        return _("Move priority of task <i>%(name)s</i> down")
    
    def get_hash(self):
        return "move-priority-down"
    
    def get_icon(self):
        return gtk.STOCK_GO_DOWN
    
    def activate(self, text=None):
        timeline = RTM_CLIENT.timelines_create()
        RTM_CLIENT.tasks_move_priority(timeline, self._list_id, self._taskseries_id, self._task_id, "down")
        
class RTMMoveToAction(deskbar.interfaces.Action):
    def __init__(self, name, from_list_id, to_list_id, to_list_name, taskseries_id, task_id):
        deskbar.interfaces.Action.__init__(self, name)
        self._from_list_id = from_list_id
        self._to_list_id = to_list_id
        self._to_list_name = to_list_name
        self._taskseries_id = taskseries_id
        self._task_id = task_id
        
    def get_verb(self):
        return _("Move task <i>%(name)s</i> to <i>%(to_list)s</i>")
        
    def get_name(self, text=None):
        return {'name': self._name, 'to_list': self._to_list_name}
        
    def get_hash(self):
        return "move-to-"+self._to_list_id
        
    def get_icon(self):
        return "go-next"
        
    def activate(self, text=None):
        timeline = RTM_CLIENT.timelines_create()
        RTM_CLIENT.tasks_move_to(timeline, self._from_list_id, self._to_list_id, self._taskseries_id, self._task_id)

class RTMTaskMatch(deskbar.interfaces.Match):
    def __init__(self, taskseries_name, list_id, taskseries_id, task_id, **args):
        deskbar.interfaces.Match.__init__ (self, name=taskseries_name, category="tasks", icon="rememberthemilk.png", **args)
        self._list_id = list_id
        self._taskseries_id = taskseries_id
        self._task_id = task_id
        
        self.add_action( RTMViewTaskAction(taskseries_name, list_id, taskseries_id, task_id), True )
        self.add_action( RTMMarkCompleteAction(taskseries_name, list_id, taskseries_id, task_id) )
        self.add_action( RTMPostponeAction(taskseries_name, list_id, taskseries_id, task_id) )
        self.add_action( RTMMovePriorityUpAction(taskseries_name, list_id, taskseries_id, task_id) )
        self.add_action( RTMMovePriorityDownAction(taskseries_name, list_id, taskseries_id, task_id) )

        for rtmlist in RTM_CLIENT.lists_get_list():
            if rtmlist.id != list_id:
                self.add_action( RTMMoveToAction(taskseries_name, list_id, rtmlist.id, rtmlist.name, taskseries_id, task_id) )
        
        self.add_action( RTMDeleteAction(taskseries_name, list_id, taskseries_id, task_id) )
        
    def get_hash(self, text=None):
        return "%s-%s-%s" % (self._list_id, self._taskseries_id, self._task_id)
        
class RTMAddTaskAction(deskbar.interfaces.Action):

    def __init__(self, name, list_id, list_name):
        deskbar.interfaces.Action.__init__(self, name)
        self._list_id = list_id
        self._list_name = list_name
        
    def get_verb(self):
        return _("Add <i>%(name)s</i> to <i>%(list)s</i>")
        
    def get_name(self, text=None):
        return {'name': self._name, 'list': self._list_name}
        
    def get_icon(self):
        return "document-new"
        
    def get_hash(self):
        return "add-task-%s-to-%s" % (self._name, self._list_name)
        
    def activate(self, text=None):
        timeline = RTM_CLIENT.timelines_create()
        RTM_CLIENT.tasks_add(timeline, self._name, self._list_id, parse=True)
     
class RTMAddTaskMatch(deskbar.interfaces.Match):
    def __init__(self, task_name, **args):
        deskbar.interfaces.Match.__init__ (self, name=task_name, category="tasks", icon="rememberthemilk.png", **args)
        
        for rtmlist in RTM_CLIENT.lists_get_list():
            self.add_action( RTMAddTaskAction(task_name, rtmlist.id, rtmlist.name) )
            
    def get_hash(self, text=None):
        return "add-task-"+self.get_name()
        
class RTMHandler(deskbar.interfaces.Module):
    
    INFOS = {'icon': load_icon("rememberthemilk.png"),
             'name': _("Remember The Milk"),
             'description': _("Search and administer your tasks from rememberthemilk.com"),
             'version': '1.1.1.0',
             "categories" : {
                "tasks": {
                       "name": _("Tasks"),
                    }
             }
    }
    INSTRUCTIONS = _("Allow Deskbar-Applet to access your Remember The Milk account")
    
    def __init__(self):
        deskbar.interfaces.Module.__init__(self)
        
    def query(self, qstring):
        auth_token = RTM_CLIENT.get_auth_token()
        # Don't query RTM if we have no auth token or if it's invalid
        if auth_token == None or RTM_CLIENT.auth_check_token(auth_token) == False:
            return
        
        tasks = RTM_CLIENT.tasks_get_list(
                   filter='status:incomplete AND (name:"%s" OR tag:"%s")' % (qstring, qstring)
        )
        matches = []
        for list in tasks:
             for taskseries in list.taskseries:
                 for task in taskseries.task:
                     matches.append(
                        RTMTaskMatch(taskseries.name, list.id, taskseries.id, task.id)
                     )
        matches.append( RTMAddTaskMatch(qstring) )
        self._emit_query_ready(qstring, matches)
        
    def has_config(self):
        return True
    
    def show_config(self, parent):
        dialog = RTMSettingsDialog(parent)
        dialog.run()
        dialog.destroy()
        
class RTMSettingsDialog(gtk.Dialog):
    def __init__(self, parent):
        gtk.Dialog.__init__(self, _("Remember The Milk settings"), parent,
                        gtk.DIALOG_DESTROY_WITH_PARENT,
                        (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
        vbox = gtk.VBox(spacing=12)
        self.vbox.pack_start(vbox)
        
        self.label = gtk.Label()
        self.label.set_justify(gtk.JUSTIFY_CENTER)
        self.label.set_line_wrap(True)
        vbox.pack_start(self.label)
        
        self.hbox = gtk.HBox(spacing=6)
        vbox.pack_start(self.hbox)
        
        self.linkbutton = gtk.LinkButton("", _("Authorize"))
        self.linkbutton.connect("clicked", self.__on_linkbutton_clicked)
        self.hbox.pack_start(self.linkbutton)
        
        self.apply_button = gtk.Button(stock=gtk.STOCK_APPLY)
        self.apply_button.connect("clicked", self.__on_button_clicked)
        self.apply_button.set_sensitive(False)
        self.hbox.pack_start(self.apply_button)

        vbox.show_all()
        self.check_access()
        
    def __on_button_clicked(self, button):
        gconf = GconfStore.get_instance().get_client()
        # Retrieve new auth token
        try:
            auth_token = RTM_CLIENT.auth_get_token(self.__frob)
            gconf.set_string(GCONF_AUTH_TOKEN_KEY, auth_token)
            infos = RTM_CLIENT.auth_check_token(auth_token)
        except RTMException:
            infos = False
	    
        if infos != False:       
            gconf.set_string(GCONF_USERNAME_KEY, infos['user']['username'])
            self.show_success()
        else:
            self.show_failed()        	
        
    def __on_linkbutton_clicked(self, linkbutton):
        url_show(linkbutton.get_uri())
        self.apply_button.set_sensitive(True)
		        
    def check_access(self):
        auth_token = RTM_CLIENT.get_auth_token()
        
        # Show instructions if we have no auth token or if it's invalid
        if auth_token == None or RTM_CLIENT.auth_check_token(auth_token) == False:
            self.show_instructions()
        else:
            self.show_success()
            
    def show_instructions(self):
        (url, frob) = RTM_CLIENT.get_auth_url('delete')
        self.__frob = frob
            
        self.linkbutton.set_uri(url)
        self.label.set_text(
        _("Press the button on the left, login with your Remember The Milk credentials and give this module access to your account. If you done with that click the button on the right.")
        )
        self.hbox.show_all()
            
    def show_success(self):
        self.hbox.hide()
        self.label.set_text( _("You granted access to your Remember The Milk account") )
        
    def show_failed(self):
        self.show_instructions()
        self.label.set_markup(_('<span weight="bold">You have to grant Deskbar-Applet access to your RTM account!</span>\n')+self.label.get_text())
    
if __name__ == '__main__':
    r = RTM_CLIENT
#    print r.get_auth_url('write')
#    bla = raw_input("Press enter if ready")
#    print r.auth_get_token()
    for list in r.tasks_get_list():
        print "List id:", list.id
        for taskseries in list.taskseries:
            print "\tTaskseries:", taskseries.id
            print "\t", taskseries.name
            for task in taskseries.task:
                print "\t\tTask id:", task.id
                print "\t\t",task.added
#    for list in r.lists_get_list():
#        print list.id, list.name
#    timeline = r.timelines_create()
#    print r.tasks_add(timeline, "Rule the world on 15.10.2007 at 15:00", parse=True)
